package it.aekidna.cleverplatypus.mediator
{
	import flash.utils.getQualifiedClassName;
	
	import mx.logging.ILogger;
	import mx.logging.Log;
	
	public class MediationRegistry
	{
		use namespace COMMAND_EVENT_INTERNAL;
		
		public static const MEDIATOR_REGISTERED : String = 
			"@MEDIATION_REGISTRY:MEDIATOR_REGISTERED";
		
		public static const PERFORMING_MEDIATION : String = 
			"@MEDIATION_REGISTRY:PERFORMING_MEDIATION";
		
		private static var _instance : MediationRegistry;
		
		private static const LOGGER : ILogger = 
			Log.getLogger( getQualifiedClassName( MediationRegistry ).replace(/:+/g, "." ) );
		
		private var _mediators : Object = {};

		public function MediationRegistry( inSingletonEnforcer : SingletonEnforcer )
		{
			_instance = this;
		}

		public static function getInstance() : MediationRegistry
		{
			if( _instance == null )
			{
				new MediationRegistry( new SingletonEnforcer() );
			}
			return _instance;
		}
		
		public function get events() : Array
		{
			var outEvents : Array = [];
			for( var eventName : String in _mediators ) 
			{
				outEvents.push( eventName );
			}
			return outEvents; 
		}
		
		public function willTrigger( inType : String ) : Boolean
		{
			return _mediators.hasOwnProperty( inType );
		}
		
		public function getMethodsByEventName( inEventName : String ) : Array
		{
			var outInfo : Array = _mediators[ inEventName ] as Array;
			if( outInfo != null )
			{
				return outInfo.concat();
			}
			return [];	
		}
		
		
		public function registerMediator( 
			inEventName : String,
			inArgs : Array,
			inMethod : Function,
			inCaller : Object ) : Boolean 
		{
			if( _mediators.hasOwnProperty( inEventName ) )
			{
				for each( var infoObj : MediationInfo in ( _mediators[ inEventName ] as Array ) )
				{
					if( infoObj.method === inMethod )
						return false;
				}
			}
			var info : MediationInfo = 
				new MediationInfo( inEventName, inArgs, inMethod, getQualifiedClassName( inCaller ).replace(/:+/g, "." ) );

			if( !_mediators.hasOwnProperty( inEventName ) )
			{
				_mediators[ inEventName ] = [];
			}
			CommandEvent.createWithNamedParams( 
				MEDIATOR_REGISTERED, 
				{ info : info } ).dispatch(); 
			( _mediators[ inEventName ] as Array ).push( info );
			LOGGER.debug( "Registered mediator for event {0}",  inEventName );
			return true;
		}
		
		public function unregisterEvent( inEventName : String ) : void
		{
			_mediators[ inEventName ] = [];
		}
		
		COMMAND_EVENT_INTERNAL function mediate( inEvent : CommandEvent ) : CommandAsyncToken
		{
			LOGGER.debug( "Mediate event dispached: {0}",  inEvent.type );
			var outToken : CommandAsyncToken = null;
			for each( var info : MediationInfo in _mediators[ inEvent.type ] as Array )
			{
				if( info == null )
				{
					LOGGER.info( "The mediation event {0} is being dispatched but it has no listeners", inEvent.type );
					continue;
				}
				var args : Array = [];
				if( inEvent.parameters != null )
				{
					for each( var argName : String in info.args )
					{
						if( !inEvent.parameters.hasOwnProperty( argName ) )
						{
							LOGGER.error( 
								"The mediation event {0} is being dispatched " +
									"with unsupported argument {1}",
								inEvent.type, argName );
							continue;
						}
						args.push( inEvent.parameters[ argName ] );
					}
				}
				if( inEvent.type != PERFORMING_MEDIATION )
				{
					CommandEvent.createWithNamedParams( 
						PERFORMING_MEDIATION, 
						{ info : info, params : args } ).dispatch();
				} 
				var result : * = info.method.apply( null, args );
				
				if( result is CommandAsyncToken ) {
					if( outToken )
					{
						LOGGER.warn( 
							"The mediation event {0} has multiple listeners " +
							" that return a CommandAsyncToken. " +
							"The token returned from the last mediation (class {1}) will be returned.", 
							inEvent.type, info.listenerObjectClass );
					}
					outToken =  result as CommandAsyncToken;
				}
			}
			inEvent.release();
			return outToken;
		}
	}
}
class SingletonEnforcer{}